using System;
using System.Collections;
using System.Xml;
using System.Xml.XPath;
using System.Globalization;

namespace MapTestFramework.Common
{
    /// <summary>
    /// Represents a helper class which provides convenience methods for manipulating xml using xpath queries.
    /// </summary>
	public static class XmlHelper
    {
        #region Public Members

        /// <summary>
        /// Gets the first node from an xml file specified by the xpath expression. Throws an exception when no node is found.
        /// </summary>
        /// <param name="xpath">XPath expression</param>
        /// <param name="sourcePath">Path of the source XML file</param>
        /// <returns>The first XmlNode that matches the XPath query.</returns>
        public static XPathNavigator GetSingleNodeOrThrowException(string xpath, string sourcePath)
        {
            ExceptionHelper.CheckEmptyOrNullValue(sourcePath, "sourcePath");

            XmlDocument document = new XmlDocument();
            document.Load(sourcePath);
            return GetSingleNodeOrThrowException(xpath, document);
        }
        /// <summary>
        /// Gets the first node from an XmlDocument object specified by the xpath expression. Throws an exception when no node is found.
        /// </summary>
        /// <param name="xpath">XPath expression</param>
        /// <param name="source">Xml source which is XPath navigable</param>
        /// <returns>XPathNavigator instance of the first node that matches the XPath query.</returns>
        public static XPathNavigator GetSingleNodeOrThrowException(string xpath, IXPathNavigable source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            XPathNavigator navigator = source.CreateNavigator();
            XPathNavigator node = navigator.SelectSingleNode(xpath);
            if (node == null)
                throw new MapTestException(
                    string.Format(CultureInfo.CurrentUICulture, 
                        Properties.Resources.ErrorMessageNoNodeForXPath, xpath
                        )
                    );
            return node;
        }
        /// <summary>
        /// Gets all the nodes from an xml file specified by the xpath expression. Throws an exception when no node is found.
        /// </summary>
        /// <param name="xpath">XPath expression</param>
        /// <param name="sourcePath">The path of the source xml file.</param>
        /// <returns>The first XmlNode that matches the XPath query.</returns>
        public static XPathNodeIterator GetAllNodesOrThrowException(string xpath, string sourcePath)
        {
            ExceptionHelper.CheckEmptyOrNullValue(sourcePath, "sourcePath");

            XmlDocument source = new XmlDocument();
            source.Load(sourcePath);
            return GetAllNodesOrThrowException(xpath, source);
        }
        /// <summary>
        /// Gets all the nodes from an XmlDocument object specified by the xpath expression. Throws an exception when no node is found.
        /// </summary>
        /// <param name="xpath">XPath expression</param>
        /// <param name="source">Xml source which is XPath navigable</param>
        /// <returns>The first XmlNode that matches the XPath query.</returns>
        public static XPathNodeIterator GetAllNodesOrThrowException(string xpath, IXPathNavigable source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            XPathNavigator navigator = source.CreateNavigator();

            XPathNodeIterator iterator = navigator.Select(xpath);
            if (iterator.Count == 0)
                throw new MapTestException(
                    ExceptionHelper.GetErrorMessage(
                        Properties.Resources.ErrorMessageNoNodeForXPath, xpath
                        )
                    );
            return iterator;
        }
        /// <summary>
		/// Removes all nodes specified by the xpath expressions from an xml file.
		/// </summary>
		/// <param name="xpaths">The xpath expressions specifying the nodes to be removed.</param>
		/// <param name="sourcePath">The path of the input file containing the message.</param>
		/// <param name="outputPath">The path of the output file to be produced.</param>
		public static void RemoveNodes(string[] xpaths, string sourcePath, string outputPath)
		{
            ExceptionHelper.CheckEmptyOrNullValue(sourcePath, "sourcePath");
            ExceptionHelper.CheckEmptyOrNullValue(outputPath, "outputPath");

            XmlDocument document = new XmlDocument();
            document.Load(sourcePath);
            RemoveNodes(xpaths, document);
            document.Save(outputPath);
            return;
		}
        /// <summary>
        /// Removes all nodes specified by the xpath expression from an xml file.
        /// </summary>
        /// <param name="xpath">The xpath expression specifying the nodes to be removed.</param>
        /// <param name="source">Xml source which is XPath navigable</param>
        public static void RemoveNodes(string[] xpaths, IXPathNavigable source)
        {
            if (xpaths == null)
                throw new ArgumentNullException("xpaths");

            if (source == null)
                throw new ArgumentNullException("source");

            foreach (string xpath in xpaths)
            {
                try
                {
                    RemoveNodes(xpath, source);
                }
                catch (XPathException ex)
                {
                    throw new MapTestException(
                        ExceptionHelper.GetErrorMessage(
                            Properties.Resources.ErrorMessageInvalidXPathQuery, 
                            xpath
                            ),
                        ex
                        );
                }
            }
            return;
        }
        /// <summary>
        /// Removes a single node specified by the xpath expression from an xml file.
        /// </summary>
        /// <param name="xpath">The xpath expression specifying the node to be removed.</param>
        /// <param name="sourcePath">The path of the input file containing the message.</param>
        /// <param name="outputPath">The path of the output file to be produced.</param>
        public static void RemoveNodes(string xpath, string sourcePath, string outputPath)
        {
            ExceptionHelper.CheckEmptyOrNullValue(sourcePath, "sourcePath");
            ExceptionHelper.CheckEmptyOrNullValue(outputPath, "outputPath");

            XmlDocument document = new XmlDocument();
            document.Load(sourcePath);
            RemoveNodes(xpath, document);
            document.Save(outputPath);
            return;
        }
        /// <summary>
        /// Removes a single node specified by the xpath expression from an XmlDocument object.
        /// </summary>
        /// <param name="xpath">The xpath expression specifying the node to be removed.</param>
        /// <param name="source">Xml source which is XPath navigable</param>
        public static void RemoveNodes(string xpath, IXPathNavigable source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            XPathNodeIterator iterator = GetAllNodesOrThrowException(xpath, source);
            DeleteNodes(iterator);

            return;
        }
        /// <summary>
        /// Updates all nodes specified by the xpath expression with the specified value. To specify a single value, use a filter expression like 'node[3]'.
        /// </summary>
        /// <param name="xpath">The xpath expression.</param>
        /// <param name="value">The value.</param>
        /// <param name="sourcePath">The path of the source xml file.</param>
        /// <param name="outputPath">The path of the output xml file.</param>
        public static void UpdateNodes(string xpath, string value, string sourcePath, string outputPath)
        {
            UpdateNodes(new string[] { xpath }, new string[] { value }, 
                sourcePath, outputPath
                );
            return;
        }
        /// <summary>
        /// Updates all nodes specified by the xpath expressions with the specified values. To specify a single value, use a filter expression like 'node[3]'.
        /// </summary>
        /// <param name="xpaths">The array of xpath expressions.</param>
        /// <param name="values">The array of values</param>
        /// <param name="sourcePath">The path of the source xml file.</param>
        /// <param name="outputPath">The path of the output xml file.</param>
        public static void UpdateNodes(string[] xpaths, string[] values, string sourcePath, string outputPath)
        {
            ExceptionHelper.CheckEmptyOrNullValue(sourcePath, "sourcePath");
            ExceptionHelper.CheckEmptyOrNullValue(outputPath, "outputPath");

            XmlDocument source = new XmlDocument();
            source.Load(sourcePath);
            UpdateNodes(xpaths, values, source);
            source.Save(outputPath);
            return;
        }
        /// <summary>
        /// Updates all nodes specified by the xpath expressions with the specified values. To specify a single value, use a filter expression like 'node[3]'.
        /// </summary>
        /// <param name="xpaths">The array of xpath expressions.</param>
        /// <param name="values">The array of values</param>
        /// <param name="source">Xml source which is XPath navigable</param>
        public static void UpdateNodes(string[] xpaths, string[] values, IXPathNavigable source)
        {
            if (xpaths == null)
                throw new ArgumentNullException("xpaths");

            for (int i = 0; i < xpaths.Length; i++)
            {
                try
                {
                    UpdateNodes(xpaths[i], values[i], source);
                }
                catch (XPathException ex)
                {
                    throw new MapTestException(
                        string.Format(CultureInfo.CurrentUICulture,
                            Properties.Resources.ErrorMessageInvalidXPathQuery, xpaths[i]
                            ),
                        ex
                        );
                }
            }
            return;
        }
        /// <summary>
        /// Updates all nodes specified by the xpath expressions with the specified values. To specify a single value, use a filter expression like 'node[3]'.
        /// </summary>
        /// <param name="xpaths">The array of xpath expressions.</param>
        /// <param name="values">The array of values</param>
        /// <param name="source">Xml source which is XPath navigable</param>
        public static void UpdateNodes(string xpath, string value, IXPathNavigable source)
        {
            if (value == null)
                RemoveNodes(xpath, source);
            else
            {
                XPathNodeIterator iterator = GetAllNodesOrThrowException(xpath, source);
                while (iterator.MoveNext())
                    iterator.Current.InnerXml = value;
            }
            return;
        }

        #endregion

        #region Helper Members

        private static void DeleteNodes(XPathNodeIterator iterator)
        {
            iterator.MoveNext();
            while (DeleteNodeAndMoveNext(iterator))
            {}
            return;
        }
        private static bool DeleteNodeAndMoveNext(XPathNodeIterator iterator)
            {
                XPathNavigator previous = iterator.Current.CreateNavigator();
            bool moveNext = iterator.MoveNext();
                previous.DeleteSelf();
            return moveNext;
            }

        #endregion
    }
}
